Skip to content

[recovery] fix: setRequestLocale ordering in [...slug] generateMetadata (static→dynamic error)#18800

Merged
pettinarip merged 1 commit into
devfrom
recovery/fix/slug-catchall-setrequestlocale-order
Jul 16, 2026
Merged

[recovery] fix: setRequestLocale ordering in [...slug] generateMetadata (static→dynamic error)#18800
pettinarip merged 1 commit into
devfrom
recovery/fix/slug-catchall-setrequestlocale-order

Conversation

@pettinarip

Copy link
Copy Markdown
Member

Production error

  • Fingerprint: grafana-fn-error-page-changed-from-static-to-dynamic-at-runtime-images-videos-clw-qfnsu
  • Message: [ERROR] ⨯ Error: Page changed from static to dynamic at runtime /images/videos/Clw-qf1sUZg.jpg, reason: headers
  • Hit count: 2
  • Stack terminates in get requestLocale (next-intl).

Root cause

/images/videos/Clw-qf1sUZg.jpg has no static asset and falls through to the catch-all app/[locale]/[...slug]/page.tsx with locale="images", slug=["videos","Clw-qf1sUZg.jpg"] (a bot/broken-link probe against a YouTube-ID-shaped path).

generateMetadata in that route never called setRequestLocale(locale) before invoking next-intl APIs:

  • the happy path getMdMetadatagetMetadata calls getTranslations("common") (src/lib/utils/metadata.ts:66);
  • the catch block also calls getTranslations("common").

Without setRequestLocale, next-intl falls back to reading the locale from request headers() for on-demand slugs (those not covered by generateStaticParams), which opts the route into dynamic rendering and throws "Page changed from static to dynamic at runtime … reason: headers".

Fix

Add setRequestLocale(locale) at the top of generateMetadata, before any next-intl call. Same class of fix as #18785 (videos slug), #18797 (wallets), #18798 (resources).

Verification

  • pnpm type-check
  • pnpm exec eslint on changed file ✅

🤖 Generated with Claude Code

@pettinarip pettinarip added the recovery-agent Created by the Recovery Agent label Jul 14, 2026
@netlify

netlify Bot commented Jul 14, 2026

Copy link
Copy Markdown

Deploy Preview for ethereumorg ready!

Name Link
🔨 Latest commit dc9c0cd
🔍 Latest deploy log https://app.netlify.com/projects/ethereumorg/deploys/6a57b6af369e0e0008c2e4f9
😎 Deploy Preview https://deploy-preview-18800.ethereum.it
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
7 paths audited
Performance: 58 (🟢 up 2 from production)
Accessibility: 95 (no change from production)
Best Practices: 100 (no change from production)
SEO: 98 (no change from production)
PWA: 59 (🔴 down 1 from production)
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

The catch-all [locale]/[...slug] generateMetadata never called
setRequestLocale before invoking next-intl APIs. getMdMetadata ->
getMetadata calls getTranslations("common"), and the catch block also
calls getTranslations("common"). Without setRequestLocale, next-intl
reads the locale from request headers for on-demand slugs, opting the
route into dynamic rendering and throwing "Page changed from static to
dynamic at runtime ... reason: headers".

Observed in production via /images/videos/Clw-qf1sUZg.jpg, which maps to
the catch-all with locale="images".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@pettinarip
pettinarip force-pushed the recovery/fix/slug-catchall-setrequestlocale-order branch from 6478b4a to dc9c0cd Compare July 15, 2026 16:34
@pettinarip
pettinarip marked this pull request as ready for review July 15, 2026 16:39
@pettinarip
pettinarip requested a review from wackerow as a code owner July 15, 2026 16:39
@github-actions

Copy link
Copy Markdown
Contributor

🔎 First-pass review — ✅ Looks mergeable

Single-line fix adding setRequestLocale(locale) at the top of generateMetadata in the [...slug] catch-all, before any next-intl call. This is the correct fix for the "Page changed from static to dynamic at runtime ... reason: headers" error: without it, on-demand slugs (not covered by generateStaticParams) make next-intl fall back to reading the locale from headers(), opting the route into dynamic rendering. Both the happy path (getMdMetadatagetTranslations) and the catch block (getTranslations("common")) are now covered.

Analysis

Generated by PR Reviewer (team) for #18800 · 67.3 AIC · ⌖ 24 AIC · ⊞ 5.2K ·

@pettinarip
pettinarip merged commit 77d47d9 into dev Jul 16, 2026
31 checks passed
@pettinarip
pettinarip deleted the recovery/fix/slug-catchall-setrequestlocale-order branch July 16, 2026 12:08
@claude claude Bot mentioned this pull request Jul 16, 2026
myelinated-wackerow added a commit that referenced this pull request Jul 16, 2026
Records the pattern behind recovery PRs #18785/#18797/#18798/#18800 and systemic fix #18811 so future work surfaces it before reintroducing the bug.

Every async Server Component page and generateMetadata under app/[locale]/ must call setRequestLocale(locale) before any next-intl API. next-intl's request config resolves the locale via requestLocale, which reads request headers unless setRequestLocale has primed the per-request cache; a next-intl call before priming opts the route into dynamic rendering and, for on-demand params (invalid-locale / unknown-slug bot probes not in generateStaticParams), throws "Page changed from static to dynamic at runtime, reason: headers". The getMetadata/getMdMetadata helpers call getTranslations("common") internally, so priming first is required even when locale is passed to them.

Canonical writeup: docs/solutions/architecture/setrequestlocale-static-to-dynamic-rendering.md (discoverable via the learnings-researcher agent). Thin pointers added in docs/best-practices.md (next-intl section) and the design-system skill (references/i18n-rtl.md + SKILL.md rule 5 and load trigger).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: wackerow <54227730+wackerow@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs review 👀 recovery-agent Created by the Recovery Agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant